﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using DotNetOpenAuth.AspNet;
using Microsoft.Web.WebPages.OAuth;
using WebMatrix.WebData;
using G28BS.Filters;
using G28BS.Models;
using System.IO;

using G28BS.Code;
using G28BS.ViewModels;

namespace G28BS.Controllers
{


    [Authorize]
    [InitializeSimpleMembership]
    public class AccountController : BaseAppController
    {
        AppDataContext db = new AppDataContext();

        private IAppRepository repo = null;
        private IUserService userService = null;


        public AccountController()
        {
            repo = new AppRepository();
            userService = new AppUserService();
        }

        public AccountController(IAppRepository mockRepo, IUserService mockUserService)
        {
            repo = mockRepo;
            userService = mockUserService;
        }


        [HttpGet]
        public ActionResult CompleteMatchHistory(int? userID)
        {

            var matchHistory = (from result in repo.gameResults 
                                where result.player.UserId == userID 
                                select result).Take(20);

            return View(matchHistory);
        }

        [HttpPost]
        public ActionResult Upload(HttpPostedFileBase photo)
        {

            // TODO: Add validation for file size and make sure the saved filename is unique.
            // Checks if filename is null, returns to edit profile page if so.
            if (photo == null)
            {
                return RedirectToAction("EditProfile", "Account");
            }

            UserProfile userp = db.UserProfiles.Find(WebSecurity.GetUserId(User.Identity.Name));

            // Store the image in current project.
            // Generate a filename to store the file on the server.

            var fileName = Guid.NewGuid().ToString() + Path.GetFileName(photo.FileName);

            var path = Path.Combine(Server.MapPath("~/Images/Profile/"), fileName );

            // Store the uploaded file on the file system.
            photo.SaveAs(path);

            // This is fine because the view does the relative path trick to, hardly modular though.
            // But while we don't have a decent database for binary files t'll do.
            userp.userImage = (fileName);
            db.SaveChanges();
        
            return RedirectToAction("MyProfile", "Account");
        }


        // Edit user information.
        [HttpPost]
        public ActionResult EditProfile(UserProfile newUserp)
        {
            UserProfile userp = db.UserProfiles.Find(newUserp.UserId);

            userp.userDescription = newUserp.userDescription;
            userp.userEmail = newUserp.userEmail;

            db.SaveChanges();

            return RedirectToAction("MyProfile", "Account");
        }

        // Deleting a user (action requested from listuser page)
        [Authorize(Roles = "Administrators, Moderators")]
        public ActionResult DeleteUser(int? id)
        {
            if (id.HasValue)
            {
                UserProfile userp = db.UserProfiles.Find(id);

                if (userp.UserName != "admin")
                {
                    foreach (var role in Roles.GetRolesForUser(userp.UserName))
                    {
                        Roles.RemoveUserFromRole(userp.UserName, role);
                    }
                    db.UserProfiles.Remove(userp);
                    db.SaveChanges();
                }
            }
            return RedirectToAction("ListUsers","Account");
        }

        // User deletes himself from database
        public ActionResult DeleteMePlease(int? id)
        {
            if (id.HasValue)
            {

                UserProfile userp = db.UserProfiles.Find(id);

                if (userp.UserName != "admin")
                {
                    foreach (var role in Roles.GetRolesForUser(userp.UserName))
                    {
                        Roles.RemoveUserFromRole(userp.UserName, role);
                    }
                    db.UserProfiles.Remove(userp);
                    WebSecurity.Logout();

                    db.SaveChanges();

                    return RedirectToAction("Index", "Home");
                }
            }
            return RedirectToAction("EditProfile", "Account");
        }


        [Authorize(Roles = "Administrators, Moderators")]
        public ActionResult DisableUser(int? id)
        {
            if (id.HasValue)
            {
                UserProfile userp = db.UserProfiles.Find(id);
                if(userp.UserName != "admin")
                {
                    userp.userState = UserStates.Disabled;
                    db.SaveChanges();
                }
            }
            return RedirectToAction("ListUsers", "Account");
        }

        [Authorize(Roles = "Administrators, Moderators")]
        public ActionResult ActivateUser(int? id)
        {
            if (id.HasValue)
            {
                UserProfile userp = db.UserProfiles.Find(id);
                userp.userState = UserStates.Active;
                db.SaveChanges();
            }
            return RedirectToAction("ListUsers", "Account");
        }

        [Authorize(Roles = "Administrators, Moderators")]
        public ActionResult MakeUserUser(int? id)
        {
            if (id.HasValue)
            {
                UserProfile userp = db.UserProfiles.Find(id);
                if (userp.UserName != "admin")
                {
                    foreach (var role in Roles.GetRolesForUser(userp.UserName))
                    {
                        Roles.RemoveUserFromRole(userp.UserName, role);
                    }
                    db.SaveChanges();
                }
            }
            return RedirectToAction("ListUsers", "Account");
        }

        [Authorize(Roles = "Administrators, Moderators")]
        public ActionResult MakeUserModerator(int? id)
        {
            if (id.HasValue)
            {
                UserProfile userp = db.UserProfiles.Find(id);
                if (userp.UserName != "admin")
                {
                    foreach (var role in Roles.GetRolesForUser(userp.UserName))
                    {
                        Roles.RemoveUserFromRole(userp.UserName, role);
                    }

                    Roles.AddUserToRole(userp.UserName, "Moderators");
                    db.SaveChanges();
                }
            }
            return RedirectToAction("ListUsers", "Account");
        }

        [Authorize(Roles = "Administrators")]
        public ActionResult MakeUserAdmin(int? id)
        {
            if (id.HasValue)
            {
                UserProfile userp = db.UserProfiles.Find(id);
                foreach (var role in Roles.GetRolesForUser(userp.UserName))
                {
                    Roles.RemoveUserFromRole(userp.UserName, role);
                }
                Roles.AddUserToRole(userp.UserName, "Administrators");
                db.SaveChanges();
            }
            return RedirectToAction("ListUsers", "Account");
        }

        [HttpGet]
        public ActionResult EditProfile()
        {
            UserProfile userp = db.UserProfiles.Find(WebSecurity.GetUserId(User.Identity.Name));
            return View(userp);
        }

        public ActionResult Index(int? id)
        {
            UserProfile userp = db.UserProfiles.Find(id);
            return View(userp);
        }

        public ActionResult MyProfile()
        {
//            AppRepository repo = new AppRepository();

            var myID = userService.GetUserID();

            var p = (from user in repo.userProfiles
                     where user.UserId == myID
                     select user).SingleOrDefault();
            // Handling the ranking "partial" view. I dont have the skills/time to make a more robust solution (Dav). 
            RankmkII myRank = (from RankmkII in repo.rankIIs 
                               where RankmkII.thisUserId == myID 
                               select RankmkII).SingleOrDefault();
            if (myRank != null)
            {
                ViewBag.tttRank = myRank.tttRank;
                ViewBag.fffRank = myRank.fffRank;
            }
            return View(p);
        }

        public ActionResult UserProfile(int? id)
        {

            var p = (from user in repo.userProfiles
                     where user.UserId == id
                     select user).SingleOrDefault();

            return View(p.SafeInformationCopy());
        }

        //
        // GET: /Account/Login
        [AllowAnonymous]
        public ActionResult Login(string returnUrl)
        {
            ViewBag.ReturnUrl = returnUrl;
            return View();
        }

        //
        // POST: /Account/Login
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Login(LoginModel model, string returnUrl)
        {

            UserProfile userp = db.UserProfiles.Find(WebSecurity.GetUserId(model.UserName));

            if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
            {
                // Don't allow user to login if disabled(banned)
                if (userp.userState == UserStates.Disabled)
                {
                    ModelState.AddModelError("", "Þessi aðgangur er óvirkur");
                    WebSecurity.Logout();
                    return View(model);
                }

                return RedirectToLocal(returnUrl);
            }

            // If we got this far, something failed, redisplay form.
            ModelState.AddModelError("", "Notandanafn eða aðgangsorð er ekki rétt.");
            return View(model);
        }

        //
        // POST: /Account/LogOff
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            WebSecurity.Logout();
            return RedirectToAction("Index", "Home");
        }

        // Listing users (only admins and moderators)
        [Authorize(Roles = "Administrators, Moderators")]
        public ActionResult ListUsers()
        {
            var ViewModel = new UserListModel();

            ViewModel.legalUsers = (from user in repo.userProfiles where user.userState == UserStates.Active orderby user.UserName ascending select user);
            ViewModel.disabledUsers = (from user in repo.userProfiles where user.userState == UserStates.Disabled orderby user.UserName ascending select user);

            return View(ViewModel);
        }

        //
        // GET: /Account/Register

        [AllowAnonymous]
        public ActionResult Register()
        {
            return View();
        }

        //
        // POST: /Account/Register

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                try
                {
                    WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
                    WebSecurity.Login(model.UserName, model.Password);
                    // Right here I will create an associated ranking. Sphagetti is our favourite food, yeah?
                    AppRepository repo = new AppRepository();

                    RankmkII newUserRank = new RankmkII(model.UserName);
                    repo.AddRankII(newUserRank);
                    repo.Save();

                    return RedirectToAction("Index", "Home");
                }
                catch (MembershipCreateUserException e)
                {
                    ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
                }
            }

            // If we got this far, something failed, redisplay form.
            return View(model);
        }

        //
        // POST: /Account/Disassociate

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Disassociate(string provider, string providerUserId)
        {
            string ownerAccount = OAuthWebSecurity.GetUserName(provider, providerUserId);
            ManageMessageId? message = null;

            // Only disassociate the account if the currently logged in user is the owner.
            if (ownerAccount == User.Identity.Name)
            {
                // Use a transaction to prevent the user from deleting their last login credential.
                using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.Serializable }))
                {
                    bool hasLocalAccount = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
                    if (hasLocalAccount || OAuthWebSecurity.GetAccountsFromUserName(User.Identity.Name).Count > 1)
                    {
                        OAuthWebSecurity.DeleteAccount(provider, providerUserId);
                        scope.Complete();
                        message = ManageMessageId.RemoveLoginSuccess;
                    }
                }
            }

            return RedirectToAction("Manage", new { Message = message });
        }

        //
        // GET: /Account/Manage

        public ActionResult Manage(ManageMessageId? message)
        {
            ViewBag.StatusMessage =
                message == ManageMessageId.ChangePasswordSuccess ? "Aðgangsorði þínu hefur verið breytt."
                : message == ManageMessageId.SetPasswordSuccess ? "Aðgangsorð þitt hefur verið geymt."
                : message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
                : "";
            ViewBag.HasLocalPassword = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
            ViewBag.ReturnUrl = Url.Action("Manage");
            return View();
        }

        //
        // POST: /Account/Manage

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Manage(LocalPasswordModel model)
        {
            bool hasLocalAccount = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
            ViewBag.HasLocalPassword = hasLocalAccount;
            ViewBag.ReturnUrl = Url.Action("Manage");
            if (hasLocalAccount)
            {
                if (ModelState.IsValid)
                {
                    // ChangePassword will throw an exception rather than return false in certain failure scenarios.
                    bool changePasswordSucceeded;
                    try
                    {
                        changePasswordSucceeded = WebSecurity.ChangePassword(User.Identity.Name, model.OldPassword, model.NewPassword);
                    }
                    catch (Exception)
                    {
                        changePasswordSucceeded = false;
                    }

                    if (changePasswordSucceeded)
                    {
                        return RedirectToAction("Manage", new { Message = ManageMessageId.ChangePasswordSuccess });
                    }
                    else
                    {
                        ModelState.AddModelError("", "Núverandi aðgangsorð er ekki rétt eða það nýja á vitlausu formi.");
                    }
                }
            }
            else
            {
                // User does not have a local password so remove any validation errors caused by a missing
                // OldPassword field
                ModelState state = ModelState["OldPassword"];
                if (state != null)
                {
                    state.Errors.Clear();
                }

                if (ModelState.IsValid)
                {
                    try
                    {
                        WebSecurity.CreateAccount(User.Identity.Name, model.NewPassword);
                        return RedirectToAction("Manage", new { Message = ManageMessageId.SetPasswordSuccess });
                    }
                    catch (Exception e)
                    {
                        ModelState.AddModelError("", e);
                    }
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

        //
        // POST: /Account/ExternalLogin

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult ExternalLogin(string provider, string returnUrl)
        {
            return new ExternalLoginResult(provider, Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
        }

        //
        // GET: /Account/ExternalLoginCallback

        [AllowAnonymous]
        public ActionResult ExternalLoginCallback(string returnUrl)
        {
            AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
            if (!result.IsSuccessful)
            {
                return RedirectToAction("ExternalLoginFailure");
            }

            if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false))
            {
                return RedirectToLocal(returnUrl);
            }

            if (User.Identity.IsAuthenticated)
            {
                // If the current user is logged in add the new account
                OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
                return RedirectToLocal(returnUrl);
            }
            else
            {
                // User is new, ask for their desired membership name
                string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId);
                ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
                ViewBag.ReturnUrl = returnUrl;
                return View("ExternalLoginConfirmation", new RegisterExternalLoginModel { UserName = result.UserName, ExternalLoginData = loginData });
            }
        }

        //
        // POST: /Account/ExternalLoginConfirmation

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult ExternalLoginConfirmation(RegisterExternalLoginModel model, string returnUrl)
        {
            string provider = null;
            string providerUserId = null;

            if (User.Identity.IsAuthenticated || !OAuthWebSecurity.TryDeserializeProviderUserId(model.ExternalLoginData, out provider, out providerUserId))
            {
                return RedirectToAction("Manage");
            }

            if (ModelState.IsValid)
            {
                // Insert a new user into the database
                using (AppDataContext db = new AppDataContext())
                {
                    UserProfile user = db.UserProfiles.FirstOrDefault(u => u.UserName.ToLower() == model.UserName.ToLower());
                    // Check if user already exists
                    if (user == null)
                    {
                        // Insert name into the profile table
                        db.UserProfiles.Add(new UserProfile { UserName = model.UserName });
                        db.SaveChanges();

                        OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
                        OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);

                        return RedirectToLocal(returnUrl);
                    }
                    else
                    {
                        ModelState.AddModelError("UserName", "Þetta notandanafn er þegar í notkun. Vinsamlegast veldu þér annað.");
                    }
                }
            }

            ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(provider).DisplayName;
            ViewBag.ReturnUrl = returnUrl;
            return View(model);
        }

        //
        // GET: /Account/ExternalLoginFailure

        [AllowAnonymous]
        public ActionResult ExternalLoginFailure()
        {
            return View();
        }

        [AllowAnonymous]
        [ChildActionOnly]
        public ActionResult ExternalLoginsList(string returnUrl)
        {
            ViewBag.ReturnUrl = returnUrl;
            return PartialView("_ExternalLoginsListPartial", OAuthWebSecurity.RegisteredClientData);
        }

        [ChildActionOnly]
        public ActionResult RemoveExternalLogins()
        {
            ICollection<OAuthAccount> accounts = OAuthWebSecurity.GetAccountsFromUserName(User.Identity.Name);
            List<ExternalLogin> externalLogins = new List<ExternalLogin>();
            foreach (OAuthAccount account in accounts)
            {
                AuthenticationClientData clientData = OAuthWebSecurity.GetOAuthClientData(account.Provider);

                externalLogins.Add(new ExternalLogin
                {
                    Provider = account.Provider,
                    ProviderDisplayName = clientData.DisplayName,
                    ProviderUserId = account.ProviderUserId,
                });
            }

            ViewBag.ShowRemoveButton = externalLogins.Count > 1 || OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
            return PartialView("_RemoveExternalLoginsPartial", externalLogins);
        }

        #region Helpers
        private ActionResult RedirectToLocal(string returnUrl)
        {
            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }

        public enum ManageMessageId
        {
            ChangePasswordSuccess,
            SetPasswordSuccess,
            RemoveLoginSuccess,
        }

        internal class ExternalLoginResult : ActionResult
        {
            public ExternalLoginResult(string provider, string returnUrl)
            {
                Provider = provider;
                ReturnUrl = returnUrl;
            }

            public string Provider { get; private set; }
            public string ReturnUrl { get; private set; }

            public override void ExecuteResult(ControllerContext context)
            {
                OAuthWebSecurity.RequestAuthentication(Provider, ReturnUrl);
            }
        }

        private static string ErrorCodeToString(MembershipCreateStatus createStatus)
        {
            // See http://go.microsoft.com/fwlink/?LinkID=177550 for
            // a full list of status codes.
            switch (createStatus)
            {
                case MembershipCreateStatus.DuplicateUserName:
                    return "Þetta notandanafn er þegar í notkun. Vinsamlegast veldu þér annað.";

                case MembershipCreateStatus.DuplicateEmail:
                    return "Notandanafn fyrir þetta netfang er þegar til. Vinsamlegast skráðu annað netfang.";

                case MembershipCreateStatus.InvalidPassword:
                    return "The password provided is invalid. Please enter a valid password value.";

                case MembershipCreateStatus.InvalidEmail:
                    return "The e-mail address provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidAnswer:
                    return "The password retrieval answer provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidQuestion:
                    return "The password retrieval question provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidUserName:
                    return "The user name provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.ProviderError:
                    return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

                case MembershipCreateStatus.UserRejected:
                    return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

                default:
                    return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
            }
        }
        #endregion
    }
}
